package org.wizard.andy;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.UUID;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

public class BluetoothConnectionManager {

	private static final String NAME = "IndovinaChi";
	private static final UUID myUUID = UUID.fromString("550e8400-e29b-41d4-a716-446655440000");
	
	private final BluetoothAdapter mAdapter;
	private final Handler handler;
	private AcceptThread acceptThread;
	private ConnectThread connectThread;
	private ConnectedThread connectedThread;
	private int mState;
	
	public static final int STATE_NONE = 0;
	public static final int STATE_LISTEN = 1;
	public static final int STATE_CONNECTING = 2;
	public static final int STATE_CONNECTED = 3;
	
	public BluetoothConnectionManager(Context context, Handler handler) {
		
		mAdapter = BluetoothAdapter.getDefaultAdapter();
		mState = STATE_NONE;
		this.handler = handler;
	}
	
	private synchronized void setState(int state) {
		mState = state;
		handler.obtainMessage(GameBoard.MESSAGE_STATE_CHANGE, state, -1).sendToTarget();
	}
	
	public synchronized int getState() {
		return mState;
	}
	
	public synchronized void start() {
		
		if (connectThread != null) { connectThread.cancel(); connectThread = null; }
		if (connectedThread != null) { connectedThread.cancel(); connectedThread = null; }
		
		setState(STATE_LISTEN);
		
		if (acceptThread == null) {
			acceptThread = new AcceptThread();
			acceptThread.start();
		}
		
	}
	
	public synchronized void connect(BluetoothDevice device) {
		
		if (mState == STATE_CONNECTING) {
			if (connectThread != null) {
				connectThread.cancel();
				connectThread = null;
			}
		}
		
		if (connectedThread != null) {
			connectedThread.cancel();
			connectedThread = null;
		}
		
		connectThread = new ConnectThread(device);
		connectThread.start();
		setState(STATE_CONNECTING);
	}
	
	public synchronized void connected(BluetoothSocket socket, BluetoothDevice device) {
		
		Log.d("ProvaBluetooth", "connected method");
		if (connectThread != null) { connectThread.cancel(); connectThread = null; }
		if (connectedThread != null) { connectedThread.cancel(); connectedThread = null; }
		if (acceptThread != null) { acceptThread.cancel(); acceptThread = null; }
		
		connectedThread = new ConnectedThread(socket);
		connectedThread.start();
		
		Message msg = handler.obtainMessage(GameBoard.MESSAGE_DEVICE_NAME);
		Bundle bundle = new Bundle();
		bundle.putString(ProvaBluetooth.DEVICE_NAME, device.getName());
		msg.setData(bundle);
		handler.sendMessage(msg);
		
		setState(STATE_CONNECTED);
		
	}
	
	public synchronized void stop() {
		
		if (connectThread != null) {
            connectThread.cancel();
            connectThread = null;
        }

        if (connectedThread != null) {
            connectedThread.cancel();
            connectedThread = null;
        }

        if (acceptThread != null) {
            acceptThread.cancel();
            acceptThread = null;
        }

        setState(STATE_NONE);
	}
	
	public void write(byte[] out) {
		
		ConnectedThread r;
		synchronized(this) {
			if (mState != STATE_CONNECTED) return;
			r = connectedThread;
		}
		r.write(out);
	}
	
	private void connectionFailed() {
		
		Message msg = handler.obtainMessage(GameBoard.MESSAGE_TOAST);
		Bundle bundle = new Bundle();
		bundle.putString(GameBoard.TOAST, "Connection Failed");
		bundle.putInt(GameBoard.TYPE_OF_FAILURE, GameBoard.CONNECTION_FAILURE);
		msg.setData(bundle);
		handler.sendMessage(msg);
		
		//BluetoothConnectionManager.this.start();
	}
	
	private void connectionLost() {
		
		Message msg = handler.obtainMessage(GameBoard.MESSAGE_TOAST);
		Bundle bundle = new Bundle();
		bundle.putString(GameBoard.TOAST, "Connection Lost");
		bundle.putInt(GameBoard.TYPE_OF_FAILURE, GameBoard.CONNECTION_LOST);
		msg.setData(bundle);
		handler.sendMessage(msg);
		
		BluetoothConnectionManager.this.start();
	}
	
	class AcceptThread extends Thread {
		
		private final BluetoothServerSocket serverSocket;
		
		public AcceptThread() {
			BluetoothServerSocket tmp = null;
			
			try {
				tmp = mAdapter.listenUsingRfcommWithServiceRecord(NAME, myUUID);
			}
			catch(IOException exc) {
				//Log
			}
			serverSocket = tmp;
		}
		
		public void run() {
			
			setName("AcceptThread");
			
			BluetoothSocket socket = null;
			
			while (mState != STATE_CONNECTED) {
				try {
					socket = serverSocket.accept();
				}
				catch(IOException ec) {
					//Log
					break;
				}
				
				if (socket != null) {
					synchronized (BluetoothConnectionManager.this) {
						
						switch(mState) {
						case STATE_LISTEN:
						case STATE_CONNECTING:
							
							connected(socket, socket.getRemoteDevice());
							break;
						case STATE_NONE:
						case STATE_CONNECTED:
							try {
								socket.close();
							}
							catch(IOException ec) {
								//log
							}
							break;
						}
						
					}
				}
			}
		}
		
		public void cancel() {
			try {
				serverSocket.close();
			}
			catch(IOException ec) {
				//Log
			}
		}
	}
	
	class ConnectThread extends Thread {
		
		private final BluetoothSocket socket;
		private final BluetoothDevice device;
		
		public ConnectThread(BluetoothDevice device) {
			this.device = device;
			BluetoothSocket tmp = null;
		
		
			try {
				tmp = device.createRfcommSocketToServiceRecord(myUUID);
			}
			catch (IOException e) {
				
			}
			socket = tmp;
		}
		
		public void run() {
			setName("ConnectThread");
			mAdapter.cancelDiscovery();
			
			//Try connection to BluetoothSocket
			try {
				socket.connect();
			}
			catch(IOException ex) {
				try {
					socket.close();
				}
				catch(IOException ex1) {
					
				}
				
				connectionFailed();
				return;
			}
			
			synchronized (ConnectThread.this) {
				connectThread = null;
			}
			
			connected(socket, device);
		}
		
		public void cancel() {
			try {
				socket.close();
			}
			catch (IOException e) {
				//Log
			}
		}
		
	}

	class ConnectedThread extends Thread {
		
		private final BluetoothSocket bluetoothSocket;
		private final InputStream inputStream;
		private final OutputStream outputStream;
		
		public ConnectedThread(BluetoothSocket socket) {
			bluetoothSocket = socket;
			InputStream tmpIn = null; 
			OutputStream tmpOut = null;
			
			try {
				tmpIn = bluetoothSocket.getInputStream();
				tmpOut = bluetoothSocket.getOutputStream();
			}
			catch(IOException e) {
				//Log
			}
			
			inputStream = tmpIn;
			outputStream = tmpOut;
		}
		
		public void run() {
			
			byte[] buffer = new byte[1024];
			int bytes;
			
			while (true) {
				try {
					
					bytes = inputStream.read(buffer);
					handler.obtainMessage(GameBoard.MESSAGE_READ, bytes, -1, buffer).sendToTarget();
				}
				catch(IOException ex) {
					connectionLost();
					BluetoothConnectionManager.this.start();
					break;
				}
			}
		}
		
		public void write(byte[] buffer) {
			try {
				
				outputStream.write(buffer);
				handler.obtainMessage(GameBoard.MESSAGE_WRITE, -1, -1, buffer).sendToTarget();
			}
			catch(IOException exc) { 
				//Log
			}
		}
		
		public void cancel() {
			try {
				bluetoothSocket.close();
			}
			catch(Exception e) {
				//log
			}
		}
		
	}
}
